package pet_pavilion.yushougeums.filter;

import cn.tedu.yushouge.commons.consts.HttpConsts;
import cn.tedu.yushouge.commons.pojo.po.UserLoginInfoPO;
import cn.tedu.yushouge.commons.security.LoginPrincipal;
import cn.tedu.yushouge.commons.web.JsonResult;
import cn.tedu.yushouge.commons.web.ServiceCode;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import pet_pavilion.yushougeums.repository.IUserJwtRepository;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

/**
 * <p>JWT过滤器</p>
 *
 * <p>此过滤器的主要作用</p>
 * <ul>
 *     <li>接收客户端提交的请求中的JWT</li>
 *     <li>尝试解析客户端提交的请求中的有效JWT</li>
 *     <li>将解析成功得到的数据创建为Authentication对象，并存入到SecurityContext中</li>
 * </ul>
 */
@Slf4j
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    @Value("${yushouge.jwt.secret-key}")
    private String secretKey;

    @Autowired
    private IUserJwtRepository userJwtRepository;

    /**
     * JWT的最小长度值
     */
    public static final int JWT_MIN_LENGTH = 113;

    public JwtAuthorizationFilter() {
        log.debug("创建过滤器类对象：JwtAuthorizationFilter");
    }

    /**
     * 放行清单
     */
    public String[] permitUrlList = {
            "/users/login"
    };

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.debug("JWT过滤器开始执行……");

        // 对放行清单中的访问直接放行
        String requestURI = request.getRequestURI();
        for (String s : permitUrlList) {
            if (requestURI.equals(s)) {
                filterChain.doFilter(request, response);
                return;
            }
        }

        // 根据业内惯用的做法，客户端提交的请求中的JWT应该存放于请求头（Request Header）中的名为Authorization属性中
        String jwt = request.getHeader(HttpConsts.HEADER_AUTHORIZATION);
        log.debug("客户端携带的JWT：{}", jwt);

        // 判断客户端是否携带了有效的JWT
        if (!StringUtils.hasText(jwt) || jwt.length() < JWT_MIN_LENGTH) {
            // 如果JWT无效，直接放行
            log.debug("客户端没有携带有效的JWT，将放行，由后续的过滤器等组件继续处理此请求……");
            filterChain.doFilter(request, response);
            return;
        }

        // 从Redis中读取用户登录信息
        UserLoginInfoPO userLoginInfoPO = userJwtRepository.getLoginInfo(jwt);
        log.debug("从Redis中读取用户的登录信息：{}", userLoginInfoPO);

        // 检查是否盗用
        String remoteAddr =  request.getRemoteAddr();
        String userAgent = request.getHeader(HttpConsts.HEADER_USER_AGENT);
        if (userLoginInfoPO == null || (!userLoginInfoPO.getRemoteAddr().equals(remoteAddr) && !userLoginInfoPO.getUserAgent().equals(userAgent))) {
            log.warn("JWT可能被盗用，将不向SecurityContext中存入任何认证信息，当前过滤器直接放行");
            filterChain.doFilter(request, response);
            return;
        }

        // 检查账号状态
        if (userLoginInfoPO.getEnable() == 0) {
            log.warn("账号状态被禁用，将JWT禁用，且响应错误");
            String message = "访问错误，账号已经被禁用！";
            response.setContentType("application/json; charset=utf-8");
            PrintWriter writer = response.getWriter();
            JsonResult<Void> jsonResult = JsonResult.fail(ServiceCode.ERR_UNAUTHORIZED_DISABLED, message);
            String jsonString = JSON.toJSONString(jsonResult);
            writer.println(jsonString);
            writer.close();
        }

        // 尝试解析JWT，并处理可能出现的异常
        Claims claims = null;
        response.setContentType("application/json; charset=utf-8");
        try {
            claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
        } catch (MalformedJwtException e) {
            String message = "非法访问！";
            log.warn("解析JWT时出现MalformedJwtException，将响应错误消息：{}", message);
            PrintWriter writer = response.getWriter();
            JsonResult<Void> jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_MALFORMED, message);
            String jsonString = JSON.toJSONString(jsonResult);
            writer.println(jsonString);
            writer.close();
            return;
        } catch (SignatureException e) {
            String message = "非法访问！";
            log.warn("解析JWT时出现SignatureException，将响应错误消息：{}", message);
            PrintWriter writer = response.getWriter();
            JsonResult<Void> jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_SIGNATURE, message);
            String jsonString = JSON.toJSONString(jsonResult);
            writer.println(jsonString);
            writer.close();
            return;
        } catch (ExpiredJwtException e) {
            String message = "您的登录信息已过期，请重新登录！";
            log.warn("解析JWT时出现ExpiredJwtException，将响应错误消息：{}", message);
            PrintWriter writer = response.getWriter();
            JsonResult<Void> jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_EXPIRED, message);
            String jsonString = JSON.toJSONString(jsonResult);
            writer.println(jsonString);
            writer.close();
            return;
        } catch (Throwable e) {
            String message = "服务器忙，请稍后再次尝试！（开发过程中，如果看到此提示，请检查控制台的信息，并补充处理异常的方法）";
            log.warn("解析JWT时出现Throwable，将响应错误消息：{}", message);
            log.warn("异常类型：{}", e.getClass());
            log.warn("异常信息：{}", e.getMessage());
            e.printStackTrace(); // 打印异常的跟踪信息，主要是为了在开发阶段更好的检查出现异常的原因
            PrintWriter writer = response.getWriter();
            JsonResult<Void> jsonResult = JsonResult.fail(ServiceCode.ERR_UNKNOWN, message);
            String jsonString = JSON.toJSONString(jsonResult);
            writer.println(jsonString);
            writer.close();
            return;
        }

        // 从解析JWT得到的Claims中获取此前存入到JWT中的数据
        Long id = claims.get("id", Long.class);
        String username = claims.get("username", String.class);
        log.debug("解析JWT结束，id={}，username={}", id, username);

        // 创建当事人对象，用于存入到Authentication对象中
        LoginPrincipal loginPrincipal = new LoginPrincipal();
        loginPrincipal.setId(id);
        loginPrincipal.setUsername(username);


        // 放行
        log.debug("JWT过滤器执行完毕，将放行");
        filterChain.doFilter(request, response);
    }

}